﻿%Chap05-Constructing a Temporary System
%UTF8
%
\chapter{构造临时系统}
\section{介绍}
本章介绍了如果构建一个最小化的Linux系统。这个系统包含恰好足够的工具来支持我们在第六章中构建最终的LFS系统，并且提供一个比最小化环境提供更方便的工作环境。

本章中的文件都安装在 \verb|$LFS/tools| 目录下，以区别于下一章中要安装的文件和主机的生产目录。既然这里编译的软件包都是暂时的，那么就没有必要污染后续的 LFS 系统。

\section{工具链技术须知}\label{Toolchain Technical Notes}
本节从基本原理和技术细节上来对整个编译方法进行解释。并没有必要立即理解本节中的所有东西。在进行了一次实际的构建后，你就能够明白大部分内容。在构建的过程中，随时可以参阅本节的内容。

第五章的总体目标是提供一个包含已被大家所知的工具集的临时空间，这些工具集可以独立于主机系统的临时空间。通过使用 后续章节中的 \verb|chroot| 命令所提供的环境，我们可以确保执行干净、正常的LFS系统的构建工作。构建的过程被设计为尽可能降低新读者操作的风险，同时也尽可能的提供最大程序的教育指引。

\begin{important}
继续之前，请先了解工作平台的名称，也就是传说中的``目标三元组''\footnote{target triplet}。检测目标三元组名称的一个简单方法就是执行各个软件包源代码中的 \verb|config.guess| 脚本。解压缩 Binutils 源代码，然后执行 脚本： \verb|./config.guess|，注意脚本的输出。例如，在现在32位Intel处理器上，你的输出可能类似于 \emph{i686-pc-linux-gnu}。

同时还需要注意平台的动态链接器的名称，通常我们也称其为动态加载器\footnote{dynamic loader}（不要与 Binutils 中包含的 标准链接器 \verb|ld| 混淆）。Glibc 所提供的动态链接器可以查找和加载程序所需的动态链接库，为程序运行做好准备工作，之后运行程序。32 位 Intel 机器上的动态链接器的名称为 \verb|ld-linux.so.2|。确定动态链接器的名称有一个万无一失的办法，就是任意挑选一个主机系统上的二进制文件，然后执行： \texttt{readelf -l <name of binary> | grep interpreter}，观察相关的输出。Glibs 源文件树根目录下的 \verb|shlib-versions| 包含了覆盖所有平台的权威参考。
\end{important}

一下是第五章中构建方法工作的关键技术要点：
\begin{itemize}
\item 通过设置 \verb|LFS_TGT| 变量来改变目标三元组的 ``vendor'' 字段，以轻微的调整工作平台的名称，从而确保第一次构建 Binutils 和 GCC 能够产生一个兼容的交叉链接器和交叉编译器。不过交叉链接器和交叉编译器产生的是兼容于当前硬件的二进制文件，而不是另一种架构下的二进制文件。

\item 临时库是通过交叉编译产生的。无法依赖主机系统上的程序是交叉编译器的天性，因为我们通过这种方法来降低主机上的头文件和库文件被集成到新工具中的几率，从而移除对目标系统潜在的污染。交叉编译同时也允许在 64 位兼容的硬件上构建 32 位和 64 位的库。

\item 仔细处理 \verb|gcc| 的控制文件（\verb|specs|），以清楚的告诉编译器要使用哪个目标动态链接器。
\end{itemize}

我们首先安装 Binutils，因为 GCC 和Glibc 的 \verb|configure| 程序会对编译器和链接器执行各种各样的特性测试，以决定启用和关闭那些软件特性。这个问题比你认为的还要重要。配置不正确的 GCC 或者 Glibc 会导致工具链出现微妙的错误，这种错误的影响甚至有可能直到最后构建整个系统的时候才会表现出来。测试套件通常可以在我们做更多额外的无用功之前及时的发现这些错误。

Binutils 在两个位置安装它的编译器和链接器， \verb|tools/bin| 和 \verb|/tools/\$LFS_TGT/bin|。一个位置中的工具硬链接到另一个位置。关于链接器的一个重要问题是他的库文件搜索顺序。详细的信息可以通过执行带有 \verb|--verbose| 的 \verb|ld| 命令来获取。例如，\texttt{ld --verbose|grep SEARCH} 可以显示当前的搜索路径以及他们的顺序。要显示 \verb|ld| 链接了哪些文件，可以编译一个伪程序（dummy program），并给编译器传递 \verb|--verbose| 开关。例如， \texttt{gcc dummy.c -W1, --verbose 2>\&1 | grep succeeded} 会显示在链接过程中成功打开的文件。

之后安装的软件包是 GCC。执行 \verb|configure| 的一个示例输出如下：
\begin{lstlisting}
checking what assembler to use... /tools/i686-lfs-linux-gnu/bin/as
checking what linker to use... /tools/i686-lfs-linux-gnu/bin/ld
\end{lstlisting}

这一步的重要性我们在上文中已有所提及。上述输出也表明了 GCC 的配置脚本不会查找 PATH 路径来寻找要使用的工具。在实际的操作中，GCC 使用的不一定是相同的路径。要查看 \verb|gcc| 使用的是哪个标准链接器，执行如下命令：\oc{gcc -print-prog-name=ld}。

\verb|gcc|~的详细信息可以通过在编译伪程序的时候传入 \textit{-v} 参数获取。例如 \oc{gcc -v dummy.c} 会显示关于处理器、编译过程、汇编步骤以及 gcc 的包含的搜索路径和搜索顺序的详细信息。

先一个安装的包是 Glibc。构建 Glibc 最主要的考虑是编译器、二进制工具以及内核头文件。通常情况下，链接器不会有什么问题，因为 Glibc 总是使用传入配置脚本
的 \verb|--host| 关联的编译器，例如，在我们上文的例子中，这个值是 \verb|i686-lfs-linux-gnu-gcc|。二进制工具和内核头文件可能有一点复杂。因此，慎重起见，我们明确指定可用的配置开关来强制启用正确的选择。执行 \verb|configure| 后，检查 \verb|glibc-build| 目录下的 \verb|config.make| 的内容，以获取重要细节的正确性。请注意我们使用 \verb|CC="i686-lfs-gnu-gcc"| 来控制使用哪个二进制工具，使用 \verb|-nostdinc| 和 \verb|-isystem| 标志来控制编译器包含文件的搜索路径。这些问题会显著 Glibc 软件包，因为 Glibc 的编译是自依赖的，通常它不需要依赖于工具链的默认配置。

Glibc 安装完成之后，需要更改 \verb|gcc| 的说明文件，使其之指向新的动态链接器 \verb|/tools/lib|。这一步很重要，因为我们要依赖它来保证搜索和链接都只会使用 \verb|/toos| 前缀的目录。动态链接器的绝对目录路径会被嵌入到每个ELF\footnote{ELF，全称为 Executable and Link Format，是Linux下通用的可执行文件格式}共享可执行文件。可以通过执行： \texttt{readelf -l <二进制文件名称> | grep interpreter} 来查看这些路径。修改 \verb|gcc| 的配置文件可以保证从现在开始到本章结束的所有要编译的文件都会使用我们指定的位于 \verb|/tools/verb| 中的新的动态链接器。

在第二轮编译 GCC 的时候，需要修改 GCC 的源代码文件，以告知 GCC 使用新的动态链接器。如果不这样做， GCC 程序将会使用内嵌在自身的主机系统 \verb|/lib| 目录下的动态链接器的名字，这就违背了我们希望脱离于主机系统的目标。

在第二轮构建 Binutils 时，我们可以使用 \verb|--with-lib-path| 配置参数来控制 \verb|ld| 的库文件搜索路径。 从这时开始，核心工具链就是自包含以及自依赖的。第五章剩余的软件包都是使用 \verb|/tools| 目录下的新的 Glibc 来构建的。

在第六章中进入 chroot 环境后，第一个要安装的主要的包就是 Glibc，因为它满足上述的自依赖的特性。一旦我们成功安装 Glic 到 texttt{/usr}，我们就对工具链的默认配置做一次快速的改变，之后继续构建 LFS 系统的剩余部分。

\section{常用编译指令}
在构建的过程中我们遵循如下声明的一些假设：
\begin{itemize}
\item 有一些包需要在编译之前就要打补丁，但是这仅限于这些补丁可以解决某个问题时才需要。有些补丁通常在本章和下章都需要，但是有时也只在本章或者其他地方才需要。因此，当你发现某些找不到关于某些下载的补丁的指令时，请不必过于担心。在应用补丁是，也会遇到关于 \emph{offst} 和 \emph{fuzz} 的警告消息。不要担心这些警告，我们的补丁已经成功应用了。

\item 在大部分软件包的编译过程中，屏幕上可能会滚动显示一些警告。这是很正常的，因为可以安全的忽略他们。这些警告就像他们出现的一样，只是关于 C 或者 C++ 声明的反对用法（但是，毕竟不是不能用的，是不是？）。C 标准改变的如此频繁，一些包仍然在使用老的标准。这会导致编译出现警告，但是不会存在问题。

\begin{important}
每安装完一个软件包之后，如果没有特别的说明，应该立即删除它的源代码和构建树。删除源代码可以防止以后安装同样的包是使用错误的配置。
\end{important}

\item 最后一次检查是否正确设置了 \texttt{LFS} 变量：
\begin{lstlisting}
echo $LFS
\end{lstlisting}
确保输出显示的是 LFS 分区的挂载目录，比如我们例子中的 \texttt{/mnt/lfs}。

\item 最后，我们还有重要的两点需要强调：
\begin{important}
构建指令都假设是我们是在使用 bash shlle 的。
 \end{important}
 
 \begin{important}
 在执行软件包的构建指令前，需要使用 \texttt{lfs}  用户解压缩源代码包，然后使用 \textbf{cd} 命令进入到源代码目录来执行后续的操作。
 \end{important}
\end{itemize}

让我们再次强调构建的流程：

\begin{mdframed}[skipabove=8pt,backgroundcolor=LightGray]
\begin{enumerate}
\item 把所有源代码和补丁放在 chroot 环境可以访问到的地方，比如 \texttt{/mnt/lfs/sources/}，\emph{不要}把源代码放在 \texttt{/mnt/lfs/tools/} 目录下。
\item 切换至源代码目录。
\item 对每一个软件包：
\begin{enumerate}[a.]
\item 使用\textbf{\texttt{tar}}程序解压缩，为构建做准备。
\item 切换至程序解压缩后得到的新目录。
\item 按本书的构建指令做构建。
\item 切换回源代码根目录。
\item 删除解压缩得到的源代码目录以及任何在构建过程中创建的 \texttt{<package>-build} 目录。
\end{enumerate}
\end{enumerate}
\end{mdframed}

\section{Binutils-2.21 - 第一轮}
Binutils 包含了一个链接器、一个编译器，以及其他处理目标文件袋额工具。

\textbf{估计构建时间：}1 SBU

\textbf{磁盘空间需求：}248 MB

\subsection{交叉 Binutils 的安装}
\begin{note}
请重新阅读前面章节的注意。理解前面标注有``重要''的这些提示的内容可以帮你避免很多后续的问题。
\end{note}
Binutils 需要时第一个构建的软件包，这一点很重要，因为 Glibc 和 GCC 需要对可用的链接器和编译器执行各种各样的测试，以确定要启用他们自身的哪些特性。

Binutils 的文档建议使用源代码目录之外的专用目录来构建 Binutils。

\begin{lstlisting}
mkdir -v ../binutils-build
cd ../binutils-build
\end{lstlisting}

\begin{note}
为了能够正常使用我们后续的 SBU 值，我们需要估计 Binutils 包从配置，知道第一次安装所花费的时间。这可以简单的通过把以下三个命令放到 \textbf{time} 命令中来实现： \textbf{time { ./configure \ldots \&\& make \&\& make install; }}。
\end{note}

\begin{note}
第五章中预估的 SBU 值和磁盘空间不包含测试套件的数据。
\end{note}

现在为编译 Binutils 做准备：
\begin{lstlisting}
../binutils-2.21/configure \
  --target=$LFS_TGT --perfix=/tools \
  --disable-nls --disable-werror
\end{lstlisting}

\textbf{配置选项的含义：}
\begin{description}
\item[\texttt{--target=\$LFS\_TGT}] \hfill \\
因为 \texttt{LFS\_TGT} 变量中的机器描述符可能与 \textbf{config.guess} 有些小差别，使用这个开关告诉 \textbf{configure} 脚本调整 Binutil 的构建系统要构建的是一个交差编译器。
\item[\texttt{--prefix=/tools}] \hfill \\
这个选项告诉配置脚本，安装 Binutils 到 /tools 目录。
\item[\texttt{--disable-nls}] \hfill \\
关闭 i18n 之类的国际化支持，因为我们构建的临时工具还用不到。
\item[\texttt{--disable-werror}] \hfill \\
这个防止因为主机编译器的警告而引起的编译停止。
\end{description}

然后就可以编译软件包：
\begin{lstlisting}
make
\end{lstlisting}

现在已经完成了编译。通常情况下我们需要执行测试套件，但是在早先阶段测试套件框架（Tcl， Expect 以及 DejaGNU）还没有办法工作。现在执行测试套件所带来的收益很微小，因为第一步中的这些程序很快就会被第二次编译的所替换。

如果在 x86\_64 系统上执行构建，需要创建符号链接以保持工具链的一致性：
\begin{lstlisting}
case $(uname -m) in
 x86_64 mkdir -v /tools/lib && ln -sv lib /tools/lib64 ;;
 esac
\end{lstlisting}

安装软件包：
\begin{lstlisting}
make install
\end{lstlisting}

关于这个包的详细信息清参考 6.12.2 节，``Binutils 的内容''。

\section{GCC-4.5.2 - 第一轮}
GCC 软件包包含了 GNU 编译器的集合，其中包含 C 和 C++ 的编译器。

\textbf{估计构建时间：} 5 SBU

\textbf{磁盘空间需求：} 80948 MB

\subsection{交叉 GCC 的安装}
现在 GCC 需要 GMP， MPFR 和 MPC 软件包。这些包可能没有包含在你的主机分发系统中，因为我们使用 GCC 来做构建。解压缩每个包的源代码到 GCC 的源代码目录，重命名这些目录，这样 GCC 就能够自动使用它们：

\begin{lstlisting}
tar -jxf ../mpfr-3.0.0.tar.bz2
mv -v mpfr-3.0.0 mpfr
tar -jxf ../gmp-5.0.1.tar.bz2
mv -v gmp-5.0.1 gmp
tar -zxf ../mpc-0.8.2.tar.gz
mv -v mpc-0.8.2 mpc
\end{lstlisting}

GCC 文档建议在 GCC 根目录之外的专用目录中构建 GCC：
\begin{lstlisting}
mkdir -v ../gcc-build
cd ../gcc-build
\end{lstlisting}

准备执行 GCC 的编译：
\begin{lstlisting}
../gcc-4.5.2/configure \
    --target=$LFS_TGT --prefix=/tools \
    --disable-nls --disable-shared --disable-multilib \
    --disable-decimal-float --disable-threads \
    --disable-libmudflap --disable-libssp \
    --disable-libgomp --enable-languages=c \
    --with-gmp-include=$(pwd)/gmp --with-gmp-lib=$(pwd)/gmp/.libs \
    --without-ppl --without-cloog
\end{lstlisting}

\textbf{配置参数的含义}
\begin{description}
\item[\texttt{--disable-shared}] \hfill \\
这个开关强制 GCC 使用静态方式链接自己内部的函数库。我们使用这种方法来避免主机系统相关的可能的问题。
\item[\texttt{--disable-decimal-float, --disable-threads, --disable-libmudflap, --disable-libssp, --disable-libgomp}] \hfill \\这些开关关闭对浮点小数、线程化、libmudflap、libssp、libgomp 等相关扩展的支持。这些特性在构建交叉编译器的时候发生编译失败，同时对于交叉编译临时 libc 的任务来说也不是必须的。
\item[\texttt{--disable-multilib}] \hfill \\
在 x86\_64 系统上， LFS 还不支持多库函数的的配置，这个开关对 x86 并没有坏处。
\item[\texttt{--enable-languages=c}] \hfill \\
这个选项确保只构建 C 编译器。这是现在我们唯一需要的语言。
\item[\texttt{--with-gmp-include=\ldots}] \hfill \\
这个选项告诉 GCC 编译器 GMP 头文件的位置。
\item[\texttt{--with-gmp-lib=\ldots}] \hfill \\
这个选项告诉 GCC 编译器 GMP 库德位置。
\item[\texttt{--without-ppl, --without-cloog}] \hfill \\
这个开关告诉 GCC 编译器不要构建 PPL 和 GLooG 库，这些库可能在主机系统中存在，但是在在 chroot 环境中可能不可用。
\end{description}

执行以下命令以编译 GCC：
\begin{lstlisting}
make
\end{lstlisting}

编译完成。正常情况下，这时可以执行测试套件，但是，正如我们在上文提到的一样，测试套件框架还没有准备好。第一次编译得到的程序很快就会被替换掉，因此执行这些测试所带来的益处很少。

安装软件包：
\begin{lstlisting}
make install
\end{lstlisting}

使用 \textit{\texttt{--disable-sharede}}，这样，编译器就不会创建和安装 \texttt{libgcc\_eh.a} 文件。Glibc 软件包依赖于这个库，因为它在自身的构建系统中使用 \textit{\texttt{-lgcc\_eh}}。因为 \texttt{libgcc.a} 文件包含了 \texttt{libgcc\_eh.a}中包含的大部分的对象，因此这个依赖可以通过创建一个指向 l\texttt{libgcc.a} 的软链接来实现。
\begin{lstlisting}
ln -vs libgcc.a `$LFS_TGT-gcc -print-libgcc-file-name | \
    sed 's/libgcc/&_eh/'`
\end{lstlisting}
关于这个软件包的详细信息请参考6.16.2节，``GCC的内容''。

\section{Linux-3.2.6 API 头文件}
Linux API 头文件（在linux-3.2.6.tar.xz中）导出了 Glibc 可用的内核 API。

\textbf{预估构建时间：} 0.1 SBU

\textbf{磁盘需求：} 511 MB

\subsection{安装 Linux API 头文件}
Linxu内核需要为系统的 C 函数库（LFS 中的 Glibc）提供API。这是通过内核源代码包中各种各样的C头文件来实现的。

确保在我们上一次行为之后，系统中不存在陈旧的文件和依赖。
\begin{cmd}
make mrproper
\end{cmd}

现在从源代码中测试并解压缩用户可见的内核头文件。他们放在本地中间目录上，在必要的时候拷贝到其他位置，因为解压缩进程会删除目标目录中已存在的文件。

\begin{cmd}
make headers_check
make INSTALL_HDR_PATH=dest headers_install
cp -rv dest/include/* /tools/include
\end{cmd}

关于这个包的详细信息请参考 6.7.2 节，“Linux API 头文件的内容。”

\section{Glibc-2.14.1}
Glibc包含了主要的C库。这个库提供了关于内存管理、路基功能查找、打开关闭文件、读写文件、字符串处理、模式匹配、算法等等的基本流程。

\compcost{5.5 SBU}{501 MB}

\subsection{安装 Glibc}
先打补丁，以避免 Glibc 使用 GCC-4.6.2 进行构建。
\begin{cmd}
patch -Np1 -i ../glibc-2.14.1-gcc_fix-1.patch
\end{cmd}

同样修正下因为此时的不完整构建环境而导致的头文件检查失败。
\begin{cmd}
patch -Np1 -i ../glibc-2.14.1-cpuid-1.patch
\end{cmd}

Glibc 文档建议使用独立于源代码目录的专用目录来进行构建。
\begin{cmd}
mkdir -v ../glibc-build
cd ../glibc-build
\end{cmd}

由于Gblic不再支持386,开发人员建议使用 -march=i486 编译标志来进行针对 x86 机器的构建。有多种办法可以完成这个任务，测试显示最好把这个标志放在构建变量 "CFLAGS" 中。使用专用的 configparms 文件，把新的标志附加到现有的 CFLAGS 内容中，而不是完全覆盖 Gblic 内部构建使用的 CFLAGS 。设置 -march时，-mtune的之同样会发生改变，因此-mtune=native 标志同样需要设置。
\begin{cmd}
case `uname -m` in
i?86) echo "CFLAGS += -march=i486 -mtune=native" > configparms ;;
esac
\end{cmd}

接下来，准备编译Glibc：
\begin{cmd}
../glibc-2.14.1/configure --prefix=/tools \
--host=\$LFS_TGT --build=\$(../glibc-2.14.1/scripts/config.guess) \
--disable-profile --enable-add-ons \
--enable-kernel=2.6.25 --with-headers=/tools/include \
libc_cv_forced_unwind=yes libc_cv_c_cleanup=yes
\end{cmd}

\textbf{配置参数的含义：}
\begin{description}
\item[--host=\$LFS\_TGT, --build=\$(../glibc-2.14.1/scripts/config.guess)] \hfill \\
这些开关结合起来会影响Glibc构建系统自身进行交叉编译，使用 /tools 中指定的交叉连接器和交叉编译器。

\item[--disable-profile] \hfill \\
构建不包含程序概要分析信息的库。如果要对临时工具进行运行概要分析，那么就忽略掉这个选项。

\item[--enable-add-ons] \hfill \\
通知Glibc使用NPTL附加组件以及它的线程库。

\item[--enable-kernel=2.6.35] \hfill \\
告诉Glibc编译支持 2.6.25 及更高版本内核的库。不再兼容低版本的内核。

\item[--with-headers=/tools/include] \hfill \\
使用最近安装的工具目录里的头文件编译自身，以使Glibc知晓内核拥有哪些特性，并且可以据此对自身进行优化。

\item[libc\_cv\_forced\_unwind=yes] \hfill \\
在5.4节-“Binutils-2.22-第一轮”是交叉编译出来的，因此在Glibc安装完成之前不能够使用。这也意味着对于强制不展开的配置测试会失败，因为它依赖于一个可用的链接器。我们传递libc\_cv\_forced\_unwind=yes变量，以通知 \textbf{configure} force\_unwind support 是可用的，不需要对其进行测试。

\item[libc\_cv\_c\_cleanup=yes] \hfill \\
简单来说，我们传递上出参数给 \textbf{configure}脚本，使得跳过该测试，并且标记 C清理句柄支持已被配置。

\end{description}

在这一步，可能会出现如下错误：
\begin{cmdout}
configure: WARNING:
*** These auxiliary programs are missing or
*** incompatible versions: msgfmt
*** some features will be disabled.
*** Check the INSTALL file for required versions.
\end{cmdout}

通常，缺少或者不兼容的\textbf{msgfmt}程序是无关紧要的。\textbf{msgfmt}程序是Gettext包的一部分，宿主操作系统应该提供了这个包。

编译包：
\begin{cmd}
make
\end{cmd}

这个确实包含有一个测试套件，但是我们现在还无法运行测试，因为到现在为止，我们还没有C++编译器。

\begin{note}
要想成功运行测试套件，还要保证本地化已经正常安装。本地化数据为系统提供了关于日期、时间、货币格等的格式信息，以及系统功能组件的输出。如果本章不打算运行测试套件（这也是我们一再的建议），那么就暂时不需要安装这些本地化数据。下一章，我们将安装合适的本地化功能。如果你坚持要安装Glibc本地化，那么请参考\secref{Glibc-2.14.1}。
\end{note}

安装包：
\begin{cmd}
make install
\end{cmd}

关于这个包的详细信息，请参考\secref{Contents of Glibc}。


\section{调整工具链}
现在我们已经安装了临时的C库，本章所有后续编译的工具都需要使用这个库进行链接。为了完成这项任务，我们需要调整工具链的规则文件，使其指向~\verb|/tools|~目录下新的动态链接器。

为了完成这个调整，我们复制编译器的规则文件\footnote{``specs file''}到一个编译默认会寻找的位置。通过~\oc|sed|~命令来进行替换，以调整GCC使用的动态链接器。

本步骤的原则是寻找所有对~\verb|/lib|~（如果你使用64位系统，那么可能是~\verb|/lib64|~）目录的引用，调整他们指向~\verb|/tools|~下的新位置。

为了提高操作的准确度，我们建议对如下的命令进行复制粘贴。请确保自己亲自检查了规则文件，已检测是否适当地调整了所引用的动态链接器的位置。必须的时候，请参考\secref{Toolchain Technical Notes}，以确认默认动态连接器的名字。

\begin{cmd}
SPECS=`dirname $($LFS_TGT-gcc -print-libgcc-file-name)`/specs
$LFS_TGT-gcc -dumpspecs | sed \
-e 's@/lib\(64\)\?/ld@/tools&@g' \
-e "/^\*cpp:$/{n;s,$, -isystem /tools/include,}" > $SPECS
echo "New specs file is: $SPECS"
unset SPECS
\end{cmd}

\begin{caution}
此时，应该立即检查工具链的基本功能函数是否工作正常（包括编译和链接）。要进行一致性检查，执行如下命令：
\begin{cmd}
echo 'main(){}' > dummy.c
$LFS_TGT-gcc -B/tools/lib dummy.c
readelf -l a.out | grep ': /tools'
\end{cmd}

如果没有错误，一切正常，最后一条命令的输出应该是下面这个样子：
\begin{cmdout}
[Requesting program interpreter: /tools/lib/ld-linux.so.2]
\end{cmdout}

需要注意上文中动态链接库的前缀是\texttt{/tools/lib}或者\texttt{/tools/lib64}（对于64位系统）。

如果输出与上文不同，或者完全没有输出，那么很抱歉，某些地方出了岔子。在继续之前，请回查之前的步骤，纠正错误的地方。有可能是上文中对规则文件的调整有问题，如果是这样，重新修改规则文件，要小心拷贝粘贴相关的命令。

所有都完成之后，清理测试文件：
\begin{cmd}
rm -v dummy.c a.out
\end{cmd}
\end{caution}

\begin{note}
我们下一节中要构建的 Binutils 可以为工具链是否正确构建提供额外的检查。如果 Binutils 构建失败了，就是提示我们之前的 Binutils、GCC 或者 Gblic 的安装有问题。
\end{note}

%Binutils-2.22 - Pass 2
\section{Binutils-2.22 - 第二轮}\label{Binutils-2.22 - Pass 2}
Binutils 包含了处理目标文件的链接器、汇编器以及其他工具。

\compcost{1.1 SBU}{363 MB}

\subsection{安装Binutils}
再次创建单独的构建目录：

\begin{cmd}
mkdir -v ../binutils-build
cd ../binutils-build
\end{cmd}

准备编译 Binutils ：
\begin{cmd}
CC="$LFS_TGT-gcc -B/tools/lib/" \
   AR=$LFS_TGT-ar RANLIB=$LFS_TGT-ranlib \
   ../binutils-2.22/configure --prefix=/tools \
   --disable-nls --with-lib-path=/tools/lib
\end{cmd}

\textbf{配置选项的含义：}
\begin{description}
\item[CC="\$LFS\_TGT-gcc -B/tools/lib/" AR=\$LFS\_TGT-ar RANLIB=\$LFS\_TGT-ranlib] \hfill \\
因为这是一次真正的原生构建\footnote{native build}，我们设置这些变量，以确保构建系统使用交叉编译器以及相关工具，而不是宿主系统的工具。
\item[--with-lib-path=/tools/lib] \hfill \\
指定编译 Binutils 使用的库文件搜索路径，\texttt{/tools/lib}会传递给链接器。这样可以防止链接器搜索宿主系统的库目录。
\end{description}

编译包：
\begin{cmd}
make
\end{cmd}

安装包：
\begin{cmd}
make install
\end{cmd}

接下来准备下一章的``重新调整''阶段使用的链接器：
\begin{cmd}
make -C ld clean
make -C ld LIB_PATH=/usr/lib:/lib
cp -v ld/ld-new /tools/bin
\end{cmd}

\textbf{\texttt{make} 参数的含义：}
\begin{description}
\item[\texttt{-C ld clean}] \hfill \\
移动所有已编译的文件到 \texttt{ld} 子目录。

\item[\texttt{-C ld LIB\_PATH=/usr/lib:/lib}] \hfill \\
重新编译 \texttt{ld} 目录下的所有文件。在命令行中指定 \texttt{LIB\_PATH} Makefile 变量允许我们覆盖掉临时工具中的默认值，并将其指向合适的最终路径。

这个变量指定了链接器默认的搜索路径。我们下一章将会用到这里的准备工作。
\end{description}

关于这个包的细节，请参考\secref{Contents of Binutils}。

%GCC-4.6.2 - Pass 2
\section{GCC-4.6.2 - 第二轮}\label{GCC-4.6.2 - Pass 2}
GCC 包包含了 GNU \index{GNU!111} 编译器集合，这个集合中包含了 C 和 C++ 编译器。

\compcost{7.0 SBU}{1.5 GB}

\subsection{安装 GCC}
对于本次编译，GCC 4.3 之后的版本会把自身当成是一个重定位的编译器，因此不允许搜索 \texttt{--prefix} 位置指定的启动文件。因为这次并不是一个重定位的编译器，而且 \texttt{/tools} 目录下的启动文件对于构建一个链接到 \texttt{/tools} 目录下的可工作的编译器是至关重要的。安装下面的补丁可以部分恢复 GCC 为旧的行为：

\begin{cmd}
patch -Np1 -i ../gcc-4.6.2-startfiles_fix-1.patch
\end{cmd}

在正常环境下，为了修复可能损坏的头文件，我们需要运行 GCC \oc{fixincludes} 脚本。但是这时候我们刚刚安装了 GCC-4.6.2 和 Glibc-2.14.1，我们也知道他们各自的头文件并不需要修复，因此并不需要运行 \oc{fixincludes} 脚本。实际上，此时运行这个脚本反倒有可能污染我们的构建环境，因为他们把宿主系统上已经修复过的头文件安装到 GCC 私有的头文件目录中。我们可以通过如下命令来禁止 \oc{fixincludes} :

\begin{cmd}
cp -v gcc/Makefile.in{,.orig}
sed 's@\./fixinc\.sh@-c true@' gcc/Makefile.in.orig > gcc/Makefile.in
\end{cmd}

对于 x86 机器，使用 \texttt{-fomit-frame-pointer} 编译器标志来构建 bootstrap 版本的 GCC，对于非 bootstrap 的构建，默认情况下会忽略该标志，目的是为了使输出的编译器看起来像被引导的样子。使用如下的 \oc{sed} 命令来强制使用该标志：
\begin{cmd}
cp -v gcc/Makefile.in{,.tmp}
sed 's/^T_CFLAGS =$/& -fomit-frame-pointer/' gcc/Makefile.in.tmp \
  > gcc/Makefile.in
\end{cmd}

下面的命令将会设置 \texttt{/tools} 下的链接器作为 GCC 的默认链接器。该命令同时会从 GCC 头文件搜索路径中移除 \texttt{/usr/include}。之所以选择现在，而不是在安装完成之后调整规则文件，是为了确保在我们实际构建 GCC 的过程中使用的是新的动态链接器，也就是，构建过程中所有的二进制文件都是使用新的 Glibc 来创建的。

\begin{cmd}
for file in \
 $(find gcc/config -name linux64.h -o -name linux.h -o -name sysv4.h)
do
  cp -uv $file{,.orig}
  sed -e 's@/lib\(64\)\?\(32\)\?/ld@/tools&@g' \
  -e 's@/usr@/tools@g' $file.orig > $file
  echo '
#undef STANDARD_INCLUDE_DIR
#define STANDARD_INCLUDE_DIR 0
#define STANDARD_STARTFILE_PREFIX_1 ""
#define STANDARD_STARTFILE_PREFIX_2 ""' >> $file
  touch $file.orig
done
\end{cmd}

如果上面的内容看起来有点困难，那么我们慢慢理清。首先，我们查找 \texttt{gcc/config} 目录下名为 \texttt{linux.h}、\texttt{linux64.h}、\texttt{sysv4.h} 的文件。对于每一查找到的文件 \verb|a| ，我们复制为 \verb|a.orig|。sed 语句的第一个表达式为 ``/lib/ld''、``/lib64/ld''、``/lib32/ld'' 增加 ``/tools'' 前缀，第二个表达式提花所有的 ``/usr'' 为 ``/tools''。\footnote{这个sed语句可以这么看，-e 表示执行语句，可以多个 -e 使用；紧跟 s 之后的作为分隔符号，因此 @ 成为了分隔符号；\& 表示匹配查找的部分，也就是指代了 /lib/ld、/lib64/ld \ldots；g表示替换命令，
/tools\&展开为 /tools/lib/ld；$\backslash$(..$\backslash$ )保存匹配的字符，如s/$\backslash$(love$\backslash$)able/$\backslash$1rs，loveable被替换成lovers；\&保存搜索字符用来替换其他字符，如 s/love/**\&**/，love则成**love**。--译者注}
之后，我们在文件的最后增加自己的定义语句，以调整头文件搜索路径以及默认的启动文件前缀。最后，我们 \oc{touch} 以更新拷贝产生的文件的时间。我们使用 \oc{cp -u}\footnote{ -u 表示仅在目标文件不存在或者源文件比目标文件新时才执行拷贝命令。--译者注} 来执行拷贝，以防止不小心运行了两次命令，意外的更新了原始文件。

对于 x86\_64 机器，取消 GCC 的 multilib \footnote{multilib 是一种属性，}规则，以禁止其尝试使用宿主系统上的库文件进行链接：

\begin{cmd}
case $(uname -m) in
  x86_64)
    for file in $(find gcc/config -name t-linux64) ; do \
      cp -v $file{,.orig}
      sed '/MULTILIB_OSDIRNAMES/d' $file.orig > $file
    done
  ;;
esac
\end{cmd}

在 GCC 第一次构建中，需要使用 GMP、MPFR、以及 MPC 包。解压缩并移动到适当的目录：

\begin{cmd}
tar -jxf ../mpfr-3.1.0.tar.bz2
mv -v mpfr-3.1.0 mpfr
tar -Jxf ../gmp-5.0.4.tar.xz
mv -v gmp-5.0.4 gmp
tar -zxf ../mpc-0.9.tar.gz
mv -v mpc-0.9 mpc
\end{cmd}

再创建一个单独的构建目录：

\begin{cmd}
mkdir -v ../gcc-build
cd ../gcc-build
\end{cmd}

在开始构建 GCC 前，记得 unset 掉那些覆盖了默认优化标志的环境变量。

现在准备编译 GCC :
\begin{cmd}
CC="$LFS_TGT-gcc -B/tools/lib/" \
    AR=$LFS_TGT-ar RANLIB=$LFS_TGT-ranlib \
    ../gcc-4.6.2/configure --prefix=/tools \
    --with-local-prefix=/tools --enable-clocale=gnu \
    --enable-shared --enable-threads=posix \
    --enable-__cxa_atexit --enable-languages=c,c++ \
    --disable-libstdcxx-pch --disable-multilib \
    --disable-bootstrap --disable-libgomp \
    --without-ppl --without-cloog \
    --with-mpfr-include=$(pwd)/../gcc-4.6.2/mpfr/src \
    --with-mpfr-lib=$(pwd)/mpfr/src/.libs
\end{cmd}

\emph{参数的含义：}
\begin{description}
\item[\texttt{--enable-clocale=gnu}] \hfill \\
这个选项确保在所有情况下都为 C++ 库选择正确的 locale 模式。一旦配置脚本发现安装了 \textit{de\_DE} 本地化环境，他就会选择正确的 gnu locale 模式。但是，如果 \textit{de\_DE} locale 没有安装，可能会导致 GCC 选择不正确的通用 locale 模式，就会存在编译出应用程序二进制接口（Application Binary Interface (ABI)）不兼容的 C++ 库的风险。

\item[\texttt{--enable-threads=posix}] \hfill \\
对多线程代码启用 C++ 异常处理。

\item[\texttt{--enable-\_\_cxa\_atexit}] \hfill \\
这个选项允许使用 \texttt{\_\_cxa\_atexit}，而不是 \texttt{atexit}来为 本地静态和全局对象注册 C++ 析构函数。

\item[\texttt{}]
\end{description}